package rpc.extension;

import lombok.extern.slf4j.Slf4j;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * @ClassName: ExtensionLoader
 * @Description: 关于dubbo的spi，每一个被spi标注的都是dubbo spi的扩展点，可以有多个内置或用户定义的实现，运行时通过配置找到具体的实现类。
 * 在ExtensionLoader.getExtensionLoader(Class<T> type)加载扩展点时，会对传入的class做校验：
 * @author: Kowk
 * @since:1.0.0
 */
@Slf4j
public final class ExtensionLoader<T> {
    //目录服务，可以在目录中配置好对应的实现
    private static final String SERVICE_DIRECTORY = "META-INF/extensions/";
    //用于装载拓展实例的装载机，每种类都有且仅有一个加载机，也许可以类比成classloader?
    private static final Map<Class<?>,ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
    //拓展的实例
    private static final Map<Class<?>,Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

    private final Class<?> type;
    //Holder可以让变量传参时改成传地址,cachedInstances即缓存实例，通常与单例有关
    private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    //缓存的类
    private final Holder<Map<String,Class<?>>> cachedClasses = new Holder<>();

    private ExtensionLoader(Class<?> type){
        this.type = type;
    }
    //S与T一样代表了任意类型，还可以使用U来表示，仅仅是一个字符，约定俗成的意义，自己想使用其他的也可以
    //拿到拓展加载器
    public static <S> ExtensionLoader<S> getExtensionLoader(Class<?> type){
        if(type == null){
            throw new IllegalArgumentException("拓展参数不可以为null");
        }
        if(!type.isInterface()){
            throw new IllegalArgumentException("拓展类型必须为interface");
        }
        if(type.getAnnotation(SPI.class) == null){
            throw new IllegalArgumentException("拓展必须为spi拓展点");
        }

        //首先在缓存中找，如果不存在就新建
        ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
        if(extensionLoader == null){
            //虽然之前判断过了不存在但是为了保险起见还是使用putIfA，插入前会判断key是否存在
            EXTENSION_LOADERS.putIfAbsent(type,new ExtensionLoader<S>(type));
            extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
        }
        return extensionLoader;
    }

    public T getExtension(String name){
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("拓展的name不可以为null或者空");
        }
        //首先在缓存中找，如果不存在就新建
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name,new Holder<>());
            holder = cachedInstances.get(name);
        }
        //如果实例不存在就创造一个单例对象
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder){
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

    private T createExtension(String name) {
        // 根据文件加载所有类型为T的拓展类并且通过name获取特定的扩展类
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw new RuntimeException("扩展名字不存在"+name);
        }

        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            try {
                EXTENSION_INSTANCES.putIfAbsent(clazz,clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
        return instance;
    }

    private Map<String,Class<?>> getExtensionClasses(){
        //从内存中获取已加载的扩展类
        Map<String,Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses){
                //双重检查，多线程下确保线程安全
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = new HashMap<>();
                    //从我们扩展目录中加载所有扩展
                    loadDirectory(classes);
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

    private void loadDirectory(Map<String, Class<?>> extensionClasses) {
        String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
        try {
            Enumeration<URL> urls;
            ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
            urls = classLoader.getResources(fileName);
            if (urls == null) {
                while(urls.hasMoreElements()){
                    URL resourceUrl = urls.nextElement();
                    loadResource(extensionClasses,classLoader,resourceUrl);
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))){
            String line;
            //读完每一行
            while ((line = reader.readLine()) != null){
                //从注释开始
                final int ci = line.indexOf('#');
                if(ci >= 0){
                    //忽略注释后面的字符串
                    line = line.substring(0,ci);
                }
                //去掉空格
                line = line.trim();
                if (line.length() > 0) {
                    try {
                        final int ei = line.indexOf('=');
                        String name = line.substring(0,ei).trim();
                        String clazzName = line.substring(ei+1).trim();
                        //由于是键值对结构，所有两者都不可为空
                        if(name.length() > 0 && clazzName.length() > 0){
                            Class<?> clazz = classLoader.loadClass(clazzName);
                            extensionClasses.put(name,clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        log.error(e.getMessage());
                    }
                }
            }
        }catch (IOException e){
            log.error(e.getMessage());
        }
    }
}
